package com.platform.common.security.service;

import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.platform.common.core.constant.CacheConstants;
import com.platform.common.core.constant.SecurityConstants;
import com.platform.common.core.constant.UserConstants;
import com.platform.common.core.domain.entity.PlatApp;
import com.platform.common.core.domain.model.DataPermissionDTO;
import com.platform.common.core.domain.model.LoginUser;
import com.platform.common.core.enums.LoginTimeEnum;
import com.platform.common.core.utils.JwtUtils;
import com.platform.common.core.utils.ServletUtils;
import com.platform.common.core.utils.StringUtils;
import com.platform.common.core.utils.ip.IpUtils;
import com.platform.common.core.utils.uuid.IdUtils;
import com.platform.common.redis.service.RedisService;
import com.platform.common.security.utils.SecurityUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Lists;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @Description: token验证处理
 * @Author: wcs
 * @Date: 2024-11-19 10:47:17
 **/
@Slf4j
@Component
public class TokenService {
    @Resource
    private RedisService redisService;

    protected static final long MILLIS_SECOND = 1000;

    /**
     * 将登陆缓存时间放到了Redis中，通过数据字典配置
     */
    protected static final String LOGIN_TIME_KEY = "sys_dict:sys_user_login_time";

    protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;

    private final static long EXPIRE_TIME = CacheConstants.EXPIRATION;

    private final static String ACCESS_TOKEN = CacheConstants.LOGIN_TOKEN_KEY;

    private final static Long MILLIS_MINUTE_TEN = CacheConstants.REFRESH_TIME * MILLIS_MINUTE;

    /**
     * 创建令牌
     */
    public Map<String, Object> createToken(LoginUser loginUser) {
        String token = IdUtils.fastUUID();
        Long userId = loginUser.getSysUser().getUserId();
        String userName = loginUser.getSysUser().getUserName();
        loginUser.setToken(token);
        loginUser.setUserid(userId);
        loginUser.setUsername(userName);
        loginUser.setIpaddr(IpUtils.getIpAddr());
        List<Long> deptIds = Optional.ofNullable(loginUser.getSysUser().getDeptIds()).orElse(Lists.newArrayList());
        loginUser.setLoginDeptIds(deptIds);
        loginUser.setOrgId(loginUser.getOrgId());
        loginUser.setDataPermissions(loginUser.getDataPermissions());

        // Jwt存储信息
        Map<String, Object> claimsMap = new HashMap<>();
        claimsMap.put(SecurityConstants.USER_KEY, token);
        claimsMap.put(SecurityConstants.DETAILS_USER_ID, userId);
        claimsMap.put(SecurityConstants.DETAILS_USERNAME, userName);

        String accessToken = JwtUtils.createToken(claimsMap);
        loginUser.setAccessToken(accessToken);

        //获取缓存失效时间
        Long expireTime = Optional.ofNullable(getLoginTimeTime(LoginTimeEnum.EXPIRE_TIME.getValue()))
                .orElse(EXPIRE_TIME);

        //登录信息版本号 随机8位字符
        String version = RandomUtil.randomString(8);
        loginUser.setVersion(version);
        redisService.setCacheObject(UserConstants.VERSION_PRE + userId + loginUser.getFlag(), version, expireTime, TimeUnit.MINUTES);
        // 缓存用户数据权限
        if (StringUtils.isNotEmpty(loginUser.getDataPermissions())) {
            Map<String, DataPermissionDTO> dataPermission = loginUser.getDataPermissions().stream()
                    .collect(Collectors.toMap(key -> String.valueOf(key.getFunctionId()), value -> value, (a, b) -> a));
            redisService.setCacheMap(CacheConstants.DATA_PERMISSION_KEY + loginUser.getToken(), dataPermission);
            loginUser.setDataPermissions(null);
        }
        refreshToken(loginUser);
        // 接口返回信息
        Map<String, Object> rspMap = new HashMap<>();
        rspMap.put("access_token", accessToken);
        rspMap.put("expires_in", expireTime);
        rspMap.put("modify_pwd_time", loginUser.getModifyPwdTime());
        rspMap.put("ip_code", loginUser.getIpCode());
        return rspMap;
    }

    /**
     * 创建令牌
     */
    public Map<String, Object> createToken(PlatApp platApp) {
        String token = IdUtils.fastUUID();

        // Jwt存储信息
        Map<String, Object> claimsMap = new HashMap<>();
        claimsMap.put(SecurityConstants.APP_KEY, platApp.getClientId());
        claimsMap.put(SecurityConstants.DETAILS_APP_ID, platApp.getAppId());
        claimsMap.put(SecurityConstants.DETAILS_APP_NAME, platApp.getAppName());

        String authToken = JwtUtils.createToken(claimsMap);
        refreshToken(platApp, authToken);
        // 接口返回信息
        Map<String, Object> rspMap = new HashMap<>();
        rspMap.put("auth_token", authToken);
        rspMap.put("expires_in", 30); // 缓存30分钟
        return rspMap;
    }

    /**
     * 刷新令牌有效期
     *
     * @param platApp 应用登录信息
     */
    public void refreshToken(PlatApp platApp, String authToken) {
        redisService.setCacheObject(CacheConstants.APP_TOKEN_KEY + authToken, platApp, 30L, TimeUnit.MINUTES);
    }

    /**
     * 获取用户身份信息
     *
     * @return 用户信息
     */
    public LoginUser getLoginUser() {
        return getLoginUser(ServletUtils.getRequest());
    }

    /**
     * 获取用户身份信息
     *
     * @return 用户信息
     */
    public LoginUser getLoginUser(HttpServletRequest request) {
        // 获取请求携带的令牌
        String token = SecurityUtils.getToken(request);
        return getLoginUser(token);
    }

    /**
     * 获取用户身份信息
     *
     * @return 用户信息
     */
    public LoginUser getLoginUser(String token) {
        LoginUser user;
        try {
            if (StringUtils.isNotEmpty(token)) {
                String userKey = JwtUtils.getUserKey(token);
                user = redisService.getCacheObject(getTokenKey(userKey));
                return user;
            }
        } catch (Exception e) {
            log.error("获取登录用户信息失败,token:{}", token);
        }
        return null;
    }

    /**
     * 校验登录用户版本
     *
     * @param loginUser 登录用户
     */
    public boolean checkVersion(LoginUser loginUser) {
        Long userId = loginUser.getSysUser().getUserId();
        Object cacheObject = redisService.getCacheObject(UserConstants.VERSION_PRE + userId + loginUser.getFlag());
        if (cacheObject == null) {
            return false;
        }
        String version = cacheObject.toString();
        String needCheck = loginUser.getVersion();
        return needCheck.equals(version);
    }

    /**
     * 设置用户身份信息
     */
    public void setLoginUser(LoginUser loginUser) {
        if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) {
            refreshToken(loginUser);
        }
    }

    /**
     * 删除用户缓存信息
     */
    public void delLoginUser(String token) {
        if (StringUtils.isNotEmpty(token)) {
            String userKey = JwtUtils.getUserKey(token);
            redisService.deleteObject(getTokenKey(userKey));
        }
    }

    /**
     * 验证令牌有效期，相差不足30分钟，自动刷新缓存
     *
     * @param loginUser 登录用户
     */
    public void verifyToken(LoginUser loginUser) {
        long expireTime = loginUser.getExpireTime();
        long refresh = Optional.of(getLoginTimeTime(LoginTimeEnum.REFRESH_TIME.getValue()) * MILLIS_MINUTE)
                .orElse(MILLIS_MINUTE_TEN);
        long currentTime = System.currentTimeMillis();
        if (expireTime - currentTime <= refresh) {
            refreshToken(loginUser);
        }
    }

    /**
     * 刷新令牌有效期
     *
     * @param loginUser 登录信息
     */
    public void refreshToken(LoginUser loginUser) {
        //获取缓存失效时间
        Long expireTime = Optional.ofNullable(getLoginTimeTime(LoginTimeEnum.EXPIRE_TIME.getValue()))
                .orElse(EXPIRE_TIME);

        loginUser.setLoginTime(System.currentTimeMillis());
        loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
        // 根据uuid将loginUser缓存
        String userKey = getTokenKey(loginUser.getToken());
        // 缓存用户数据权限
        if (StringUtils.isNotEmpty(loginUser.getDataPermissions())) {
            Map<String, DataPermissionDTO> dataPermission = loginUser.getDataPermissions().stream()
                    .collect(Collectors.toMap(key -> String.valueOf(key.getFunctionId()), value -> value, (a, b) -> a));
            loginUser.setDataPermissions(null);
            redisService.setCacheMap(CacheConstants.DATA_PERMISSION_KEY + loginUser.getToken(), dataPermission);
        }
        redisService.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
        redisService.setCacheObject(UserConstants.VERSION_PRE + loginUser.getUserid() + loginUser.getFlag(), loginUser.getVersion(), expireTime, TimeUnit.MINUTES);
    }

    private String getTokenKey(String token) {
        return ACCESS_TOKEN + token;
    }

    public Map<String, Object> createRedirectToken(LoginUser loginUser) {

        String redirectToken = IdUtils.fastUUID();
        String userKey = getTokenKey(redirectToken);
        redisService.setCacheObject(userKey, loginUser, 1L, TimeUnit.MINUTES);

        // 接口返回信息
        Map<String, Object> rspMap = new HashMap<>();
        rspMap.put("redirect_token", redirectToken);
        return rspMap;
    }

    public Map<String, Object> getRedirectToken(String redirectToken) {
        //获取缓存失效时间
        Long expireTime = Optional.ofNullable(getLoginTimeTime(LoginTimeEnum.EXPIRE_TIME.getValue()))
                .orElse(EXPIRE_TIME);
        // 接口返回信息
        Map<String, Object> rspMap = new HashMap<>();
        String access_token = "";
        String userKey = getTokenKey(redirectToken);
        Object objLoginUser = redisService.getCacheObject(userKey);
        if (objLoginUser != null) {

            LoginUser loginUser = (LoginUser) objLoginUser;
            access_token = loginUser.getAccessToken();
            redisService.deleteObject(redirectToken);

        }
        rspMap.put("access_token", access_token);
        rspMap.put("expires_in", expireTime);
        return rspMap;
    }

    public DataPermissionDTO getDataPermissionByFunctionId(Long functionId, LoginUser loginUser) {
        if (Objects.isNull(functionId)) {
            return null;
        }
        Object cacheMapValue = redisService.getCacheMapValue(CacheConstants.DATA_PERMISSION_KEY + loginUser.getToken(), String.valueOf(functionId));
        return (DataPermissionDTO) cacheMapValue;
    }

    /**
     * 获取登陆缓存时间/刷新缓存时间
     *
     * @param timeType token相关配置key
     * @return token配置时间
     */
    private Long getLoginTimeTime(String timeType) {
        //从Redis中获取登陆相关缓存时间，修改入口在数据字典处修改
        JSONArray loginTimeCache = redisService.getCacheObject(LOGIN_TIME_KEY);
        Map<String, Long> loginTimeCacheMap = new HashMap<>(2);
        for (int i = 0; i < loginTimeCache.size(); i++) {
            JSONObject timeCache = loginTimeCache.getJSONObject(i);
            String key = timeCache.getString("dictLabel");
            Long value = timeCache.getLong("dictValue");
            if (key != null && value != null) {
                loginTimeCacheMap.put(key, value);
            }
        }
        return loginTimeCacheMap.get(timeType);
    }
}
